'use strict'

const { homedir } = require('os')
const path = require('path')
const inquirer = require('inquirer')
const fse = require('fs-extra')
const ejs = require('ejs')
const glob = require('glob')
const semver = require('semver')
const kebabCase = require('kebab-case')
const Command = require('@imooc-yishen-cli-dev/command')
const Package = require('@imooc-yishen-cli-dev/package')
const log = require('@imooc-yishen-cli-dev/log')
const {
  spinnerStart,
  sleep,
  spawnAsync,
} = require('@imooc-yishen-cli-dev/utils')
const getProjectTemplate = require('./getProjectTemplate')

const TYPE_PROJECT = 'project'
const TYPE_COMPONENT = 'component'

const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'

const WHITE_COMMAND = ['npm', 'cnpm', 'yarn', 'pnpm']

const prompt = inquirer.createPromptModule()

class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || ''
    this.force = this._argv[1]?.force
    log.verbose('projectName', this.projectName)
    log.verbose('force', this.force)
  }
  async exec() {
    const projectInfo = await this.prepare()
    if (projectInfo) {
      log.verbose('projectInfo', projectInfo)
      this.projectInfo = projectInfo
      // 下载模板
      await this.downloadTemplate()
      // 安装模板
      await this.installTemplate()
    }
  }

  async prepare() {
    // 0. 判断项目模板是否存在
    const template = await getProjectTemplate()
    if (!template || template.length === 0) {
      throw new Error('项目模板不存在')
    }
    this.template = template
    // 1. 判断当前目录是否为空
    const localPath = process.cwd()
    if (!this.isDirEmpty(localPath)) {
      let ifContinue = false
      if (!this.force) {
        // 询问是否继续创建
        ifContinue = (
          await prompt({
            type: 'confirm',
            name: 'ifContinue',
            default: false,
            message: `当前文件夹不为空，是否继续创建项目？(${localPath})`,
          })
        ).ifContinue
        if (!ifContinue) {
          return
        }
      }
      // 2. 是否启动强制更新
      if (ifContinue || this.force) {
        // 给用户做二次确认
        const { confirmDelete } = await prompt({
          type: 'confirm',
          name: 'confirmDelete',
          default: false,
          message: '是否确认清空当前目录下的文件？',
        })
        if (confirmDelete) {
          // 清空当前目录
          fse.emptyDirSync(localPath)
        }
      }
    }
    return this.getProjectInfo()
  }

  async getProjectInfo() {
    function isValidName(v) {
      return /^[a-zA-Z]+([-][a-zA-Z0-9][a-zA-Z0-9]*|[_][a-zA-Z0-9][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
        v
      )
    }

    let projectInfo = {}
    let isProjectNameValid = false
    if (isValidName(this.projectName)) {
      isProjectNameValid = true
      projectInfo.projectName = this.projectName
    }

    // 选择创建项目或组件
    const { type } = await prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型?',
      default: TYPE_PROJECT,
      choices: [
        { value: TYPE_PROJECT, name: '项目' },
        { value: TYPE_COMPONENT, name: '组件' },
      ],
    })
    const title = type === TYPE_PROJECT ? '项目' : '组件'
    this.template = this.template.filter((template) => {
      return template.tag.includes(type)
    })
    // 输入项目/组件名称
    const projectNamePrompt = {
      type: 'input',
      name: 'projectName',
      message: `请输入${title}名称`,
      // TODO 测试时default 设置为q1,上线前记得修改为空
      default: 'q1W2C3',
      validate: function (v) {
        const done = this.async()
        setTimeout(function () {
          if (!isValidName(v)) {
            done(`请输入合法的${title}名称`)
            return
          }
          done(null, true)
        }, 0)

        // 首字符必须为英文字符
        // 尾字符必须为英文或数字，不能为字符
        // 字符仅允许“-_”
        return /^[a-zA-Z]+([-][a-zA-Z0-9][a-zA-Z0-9]*|[_][a-zA-Z0-9][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
          v
        )
      },
      filter: function (v) {
        return v
      },
    }
    let projectPrompt = []
    if (isProjectNameValid) {
      projectPrompt.push(projectNamePrompt)
    }
    projectPrompt.push(
      // 输入项目版本号
      {
        type: 'input',
        name: 'projectVersion',
        message: `请输入${title}版本号`,
        default: '1.0.0',
        validate: function (v) {
          const done = this.async()
          setTimeout(function () {
            if (!semver.valid(v)) {
              done(`请输入合法的${title}名称`)
              return
            }
            done(null, true)
          }, 0)
          return !!semver.valid(v)
        },
        filter: function (v) {
          if (!!semver.valid(v)) {
            return semver.valid(v)
          } else {
            return v
          }
        },
      },
      // 选择项目模板
      {
        type: 'list',
        name: 'projectTemplate',
        message: `请选择初始化${title}模板`,
        choices: this.createTemplateChoices(),
      }
    )
    if (type === TYPE_PROJECT) {
      // 获取项目的基本信息
      const project = await prompt(projectPrompt)
      projectInfo = {
        ...projectInfo,
        type,
        ...project,
      }
    } else if (type === TYPE_COMPONENT) {
      const descriptionPrompt = {
        type: 'input',
        name: 'componentDescription',
        message: '请输入组件描述',
        validate: function (v) {
          const done = this.async()
          setTimeout(function () {
            if (!v) {
              done('请输入组件描述')
              return
            }
            done(null, true)
          }, 0)
          return v
        },
      }
      projectPrompt.push(descriptionPrompt)
      // 获取组件的基本信息
      const component = await prompt(projectPrompt)
      projectInfo = {
        ...projectInfo,
        type,
        ...component,
      }
    }
    // 生成classname projectName -> project-name
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName
      projectInfo.className = kebabCase(projectInfo.projectName).replace(
        /^-/,
        ''
      )
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion
    }
    if (projectInfo.componentDescription) {
      projectInfo.description = projectInfo.componentDescription
    }
    return projectInfo
    // return 项目的基本信息 object
  }

  async downloadTemplate() {
    const templateInfo = this.template.find(
      (item) => item.npmName === this.projectInfo.projectTemplate
    )
    this.templateInfo = templateInfo
    const targetPath = path.resolve(homedir(), '.imooc-cli-dev', 'template')
    const storeDir = path.resolve(
      homedir(),
      '.imooc-cli-dev',
      'template',
      'node_modules'
    )
    const templateNpm = new Package({
      targetPath,
      storeDir,
      packageName: templateInfo.npmName,
      packageVersion: templateInfo.version,
    })
    if (await templateNpm.exists()) {
      const spinner = spinnerStart('正在更新模板..')
      try {
        await templateNpm.update()
        log.success('更新模板成功！')
        this.templateNpm = templateNpm
      } catch (error) {
        throw error
      } finally {
        spinner.stop(true)
        if (await templateNpm.exists()) {
          log.success('更新模板成功')
          this.templateNpm = templateNpm
        }
      }
      spinner.stop(true)
    } else {
      const spinner = spinnerStart('正在下载模板..')
      try {
        await templateNpm.install()
      } catch (error) {
        throw error
      } finally {
        spinner.stop(true)
        if (await templateNpm.exists()) {
          log.success('下载模板成功')
          this.templateNpm = templateNpm
        }
      }
    }
  }

  async installTemplate() {
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        this.templateInfo.type === TEMPLATE_TYPE_NORMAL
      }
      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        await this.installNormalTemPlate()
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        await this.installCustomTemPlate()
      } else {
        throw new Error('无法识别项目模板信息！')
      }
    } else {
      throw new Error('项目信息不存在！')
    }
  }

  checkCommand(command) {
    if (WHITE_COMMAND.includes(command)) {
      return command
    }
    return null
  }

  async execCommand(command, errorMsg) {
    let ret
    if (command) {
      const cmdArray = command.split(' ')
      const cmd = this.checkCommand(cmdArray[0])
      if (!cmd) {
        throw new Error(`命令不存在！命令：${command}`)
      }
      const args = cmdArray.slice(1)
      ret = await spawnAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd(),
      })
    }
    if (ret !== 0) {
      throw new Error(errorMsg)
    }
    return ret
  }

  ejsRender({ ignore }) {
    const dir = process.cwd()
    const projectInfo = this.projectInfo
    return new Promise((resolve, reject) => {
      glob(
        '**',
        {
          cwd: dir,
          ignore,
          nodir: true,
        },
        (err, files) => {
          if (err) {
            reject(err)
          }
          // console.log(files)
          Promise.all(
            files.map((file) => {
              const filePath = path.resolve(dir, file)
              return new Promise((resolve1, reject1) => {
                ejs.renderFile(filePath, projectInfo, (err, result) => {
                  if (err) {
                    reject1(err)
                  } else {
                    fse.writeFileSync(filePath, result)
                    resolve1(result)
                  }
                })
              })
            })
          )
            .then(() => {
              resolve()
            })
            .catch((err) => {
              reject(err)
            })
        }
      )
    })
  }

  async installNormalTemPlate() {
    // 拷贝模板代码至当前目录
    const templatePath = path.resolve(
      this.templateNpm.cacheFilePath,
      'template'
    )
    const targetPath = process.cwd()
    fse.ensureDirSync(templatePath)
    fse.ensureDirSync(targetPath)
    fse.copySync(templatePath, targetPath)
    const templateIgnore = this.templateInfo.ignore || []
    const ignore = ['**/node_modules/**', ...templateIgnore]
    // 模板渲染
    await this.ejsRender({ ignore })
    // 依赖安装
    const { installCommand, startCommand } = this.templateInfo
    await this.execCommand(installCommand, '依赖安装过程错误！')
    // 启动命令执行
    await this.execCommand(startCommand, '启动执行过程失败！')
  }

  async installCustomTemPlate() {
    // 查询自定义模板的入口文件
    if (await this.templateNpm.exists()) {
      const rootFile = this.templateNpm.getRootFilePath()
      if (fs.existsSync(rootFile)) {
        log.notice('开始执行自定义模板')
        const templatePath = path.resolve(
          this.templateNpm.cacheFilePath,
          'template'
        )
        const options = {
          templateInfo: this.templateInfo,
          projectInfo: this.projectInfo,
          sourcePath: templatePath,
          targetPath: process.cwd(),
        }
        const code = `require('${rootFile}')(${JSON.stringify(options)})`
        log.verbose('code', code)
        await spawnAsync('node', ['-e', code], {
          stdio: 'inherit',
          cwd: process.cwd(),
        })
        log.success('自定义模板安装成功')
      } else {
        throw new Error('自定义模板入口文件不存在！')
      }
    }
  }

  isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath)
    // 文件过滤的逻辑
    fileList = fileList.filter(
      (file) => !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
    )
    return !fileList || fileList.length <= 0
  }

  createTemplateChoices() {
    return this.template.map((item) => ({
      value: item.npmName,
      name: item.name,
    }))
  }
}

function init(argv) {
  return new InitCommand(argv)
}

module.exports = init
module.exports.InitCommand = InitCommand
